Skip to content

Fix counting *scanf() format string placeholders#5594

Open
hakre wants to merge 2 commits intophpstan:2.1.xfrom
hakre:patch-1
Open

Fix counting *scanf() format string placeholders#5594
hakre wants to merge 2 commits intophpstan:2.1.xfrom
hakre:patch-1

Conversation

@hakre
Copy link
Copy Markdown

@hakre hakre commented May 3, 2026

Update PrintfHelper.php:

Make public function getScanfPlaceholdersCount(string $format): ?int returning the sscanf() vetted number of placeholders that give/return/assign conversions.

refs:

@hakre hakre force-pushed the patch-1 branch 4 times, most recently from 57da830 to 5742f2b Compare May 3, 2026 19:24
@hakre hakre marked this pull request as draft May 5, 2026 19:34
@hakre hakre changed the title Counting sscanf/fscanf format string placeholders Fix counting sscanf/fscanf format string placeholders May 5, 2026
@hakre hakre marked this pull request as ready for review May 6, 2026 02:59
@phpstan-bot
Copy link
Copy Markdown
Collaborator

This pull request has been marked as ready for review.

Comment thread src/Rules/Functions/PrintfHelper.php Outdated
$throws = $this->phpVersion->throwsValueErrorForInternalFunctions();
try {
if ($throws === false) {
set_error_handler(
Copy link
Copy Markdown
Contributor

@staabm staabm May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this error-handler/try-catch construction?
we don't need it in other rules/rule-helpers?

Copy link
Copy Markdown
Contributor

@staabm staabm May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we use @sscanf() instead and check the return type?

Copy link
Copy Markdown
Author

@hakre hakre May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this error-handler/try-catch construction?

To support the runtime versions phpstan is running with (7.2 < 8.2 (dev) > 8.... IIRC)

we don't need it in other rules/rule-helpers?

I don't know ... maybe this is automatically downported by the easy downgrader?

could we use @sscanf() instead and check the return type?

I just had the same question/idea and wanted to ask you if that would be acceptable by code-style in phpstan.

For the technical question I'll explore this option and try to figure out a speaking snippet that runs on 3v4l.org if you like.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have other places where we swallow runtime warnings with @ in case there is no other option.
(there should be a test-case in the PR, which fails when the @ gets removed)

Copy link
Copy Markdown
Author

@hakre hakre May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @-suppression operator looks like a technically feasible option for PHP 4.3.0 - 7.4.33 and the try-catch block is required for 8.0.0 - 8.5.3. (refs 3v4l.org/WR85Q).

The $return === null case is already covered in the code-paths, so the @-suppression operator could be introduced in front of the call to sscanf() and the error handler set-up (before) and tear-down (closer) be removed.

This would still need to pass the make, but this is how it could look like in (the relevant) code:

@@ -41,27 +41,19 @@
 
 	public function getScanfPlaceholdersCount(string $format): ?int
 	{
-		$throws = $this->phpVersion->throwsValueErrorForInternalFunctions();
-		try {
-			if ($throws === false) {
-				set_error_handler(
-					static function ($s, $m) {
-						throw new ValueError($m, 0);
-					},
-				);
-			}
-			$result = sscanf('', '%*n' . $format);
-			if ($result === null) {
-				return null;
-			}
-			return count($result);
-		} catch (ValueError) {
-			return null;
-		} finally {
-			if ($throws === false) {
-				restore_error_handler();
-			}
-		}
+		if ($this->phpVersion->throwsValueErrorForInternalFunctions()) {
+			try {
+				$result = sscanf('', '%*n' . $format);
+			} catch (ValueError) {
+				return null;
+			}
+		} else {
+			$result = @sscanf('', '%*n' . $format);
+		}
+		if ($result === null) {
+			return null;
+		}
+		return count($result);
 	}
 
 	/**

this is with an alignment to the other existing routine if(phpVersion->) direct call style.

It would spare the following uses:

  • function restore_error_handler
  • function set_error_handler

I'll push this once it passes. @staabm

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please find the code updated for the use of the @-suppression operator as well.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have other places where we swallow runtime warnings with @ in case there is no other option. (there should be a test-case in the PR, which fails when the @ gets removed)

Oh I would love to add that, or this this with phpinfection covered in the CI? Just found the infection target, but it seems it's not part of the main goal?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this done with the @-suppression testing? Forgot to ask.

Copy link
Copy Markdown
Contributor

@staabm staabm May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sure we have a test which is run on CI PHP 7.x and uses a invalid pattern which would emit a warning on PHP 7.x.

PHPUnit will emit a new warning/error when the code under test emits warnings

Comment on lines +38 to +39
sscanf($str, '%.E', $number); // bad scan conversion character '.'
fscanf($str, '%.E', $number); // bad scan conversion character '.'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sscanf($str, '%.E', $number); // bad scan conversion character '.'
fscanf($str, '%.E', $number); // bad scan conversion character '.'
sscanf($str, '%.E', $number); // bad scan conversion character '.'
fscanf($resource, '%.E', $number); // bad scan conversion character '.'

Copy link
Copy Markdown
Author

@hakre hakre May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative suggestion: $resource -> STDIN in the file as in PHPStan/Rules/Functions/data/bug-10260.php

Please find it updated.

Copy link
Copy Markdown
Contributor

@staabm staabm May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would stay with $resource which the tests used before. pre-existing tests should not be changed whenever possible (only expectations should change). variable or constant might have different implications in static analysis context

Copy link
Copy Markdown
Author

@hakre hakre May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would stay with $resource which the tests used before. pre-existing tests should not be changed whenever possible (only expectations should change). variable or constant might have different implications in static analysis context

Yes, was also wondering about that so I tried it out ... please find the $str -> $resource by-catch fix updated, I missed your comment for the previous push so please excuse my ignorance re-requesting review a bit early.

@hakre hakre changed the title Fix counting sscanf/fscanf format string placeholders Fix counting *scanf() format string placeholders May 6, 2026
@hakre
Copy link
Copy Markdown
Author

hakre commented May 6, 2026

Do you know by chance why https://github.com/phpstan/phpstan-src/actions/runs/25431166468/job/74597885107 keeps failing on my changes? I'd like to better understand @staabm

@hakre hakre requested a review from staabm May 6, 2026 12:20
hakre added 2 commits May 6, 2026 14:29
Additional by-catch fix of a variable misnomer in 023fa08 ("Fix printf
parameters rule", 2017-04-02) spotted during review.
Update PrintfHelper.php to make

    public function getScanfPlaceholdersCount(string $format): ?int`

return the sscanf() vetted number of placeholders that return/assign
conversions.

Addresses long-standing regressions reported originally in
[phpstan-10260].

References:

- [phpstan-10260]
- phpstan/phpstan#10260 (comment)
- [phpstan-src-5591]
- [phpstan-14567]
- https://3v4l.org/WR85Q

[phpstan-10260]: phpstan/phpstan#10260
[phpstan-src-5591]: phpstan#5591
[phpstan-14567]: phpstan/phpstan#14567
@staabm
Copy link
Copy Markdown
Contributor

staabm commented May 6, 2026

Do you know by chance why phpstan/phpstan-src/actions/runs/25431166468/job/74597885107 keeps failing on my changes?

we see the same errors on others PRs targeting 2.1.x -> this means these errors are not related to your PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants